#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <windows.h>
#include <Windows.h>
#include <algorithm>
#include <vector>
#include <string.h>
#include <time.h>

#define NUMINTS (1000)
#define FILESIZE (NUMINTS * sizeof(int))
#define _BUFFER_SIZE 1024
#define _BUFFERSIZE (1*1024*1024)
#define _CANT_CREATE_LOG_FILE 101
#define _TOO_FEW_ARGS 6
#define _FILE_NOT_EXISTS 7
#define _ACCESS_CHECK 8
#define _EMPTY_FILE 9
#define _MAX_FILE_SIZE 10000000
#define _MOD 100000
using namespace std;


struct struct_logmsg {
	public:
		int level;
		string msg;
		SYSTEMTIME time;
};

char *logFile = new char[100];
vector<struct_logmsg> logList;
int logLevel;
bool bStratg = false, lastComa = false;

bool cmpFunc (int i,int j) { return (i<j); }

enum ErrorLevels {
	_INFO,
	_WARN,
	_ERROR,
	_MAX_LEVEL
};

class Singleton {
	protected:
		static Singleton* _self;
		Singleton() {}
		virtual ~Singleton() {}
	public:
		static Singleton* Instance(){
			if (!_self)
				_self = new Singleton();
			return _self;
		}
		static bool DeleteInstance(){
			if(_self){
				delete _self;
				_self = 0;
				return true;
			}
			return false;
		}
};
 
Singleton* Singleton ::_self = 0;

static class LogFile {
	HANDLE hLogFile;
	LPCTSTR lpFileName;
	FILE *fLogFile;
	public: 
		
	~LogFile(){
		//fclose(fLogFile);
		logList.clear();
	}

	void AddLogMessage();

	LogFile () {
	//	fLogFile = fopen(logFile, "w");
	}
	
	void PrintAllToLogFile(){
		for ( int i = 0; i<(int)logList.size(); i ++ ){
		//	fprintf(fLogFile, "%d ", logList[i].level);
			fprintf(fLogFile, "%d$ %d:%d:%d-%d, %s\n", logList[i].level, logList[i].time.wHour, logList[i].time.wMinute, logList[i].time.wSecond, logList[i].time.wMilliseconds,
			logList[i].msg.c_str());
		}
	}
	static struct_logmsg FormLogMsg(string textMsg, int msgLevel){
		struct_logmsg newLogMsg;
		newLogMsg.msg = textMsg;
		newLogMsg.level = msgLevel;
		GetSystemTime (&newLogMsg.time);
		return newLogMsg;
	}
	static void AddLogMessage(char *tmpMessage, int errLevel){
		char *textMessage = new char[_BUFFER_SIZE];
		strcpy (textMessage, tmpMessage);
		WCHAR MessageBuffer[_BUFFER_SIZE];

		DWORD LastErrorCode = GetLastError();
		SYSTEMTIME lpSysTime;

		if (errLevel == _INFO && logLevel <= _INFO){
			strcat(textMessage, " OK ");
			struct_logmsg newLogMsg = FormLogMsg(textMessage, _INFO);
			logList.push_back(newLogMsg);
		}
		if (errLevel == _WARN && logLevel <= _WARN){
			strcat(textMessage, " Warning ");
			struct_logmsg newLogMsg = FormLogMsg(textMessage, _WARN);
			logList.push_back(newLogMsg);
		}
		if (errLevel == _ERROR && logLevel <= _ERROR){
			strcat(textMessage, " Failed ");
			FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL, LastErrorCode, LANG_ENGLISH, (LPWSTR)MessageBuffer, 0, NULL);

			wprintf(L"%s\n", MessageBuffer);
		//	sprintf(textMessage, "%s $%s", textMessage, MessageBuffer);
			struct_logmsg newLogMsg = FormLogMsg(textMessage, _ERROR);
			logList.push_back(newLogMsg);
		}
		
		delete [] textMessage;
		LocalFree(MessageBuffer);
	}
};

int ConvertInputToInts(LPSTR pMapFile, long long numberOfChars, vector<int> &vecNumInts, int &tail, char &divider, bool &first, LogFile *myLogFile) {
    long currPos = 0;
    unsigned long tmp = tail;
    int numberOfInts = 0;

	while (currPos < numberOfChars) {
		char currChar = pMapFile[currPos];
		if (currChar != divider) {
			first = false;
			if (currChar >= '0' && currChar <= '9') {
				tmp = tmp * 10 + (currChar - '0');
			} else {
				//char strLog;
				//sprintf(&strLog, "Character %c at position %li", currChar, currPos);
				//myLogFile->AddLogMessage(&strLog, _ERROR);                        
			}
			currPos++;
		} else {
			if (first) {
				currPos++;
			} else {
				if (tmp <= UINT_MAX) {
					vecNumInts.push_back(tmp);
					numberOfInts++;
					tmp = 0;
					currPos++;
					first = true;
				} else {
					//char strLog;
					//sprintf(&strLog, "Element %li at position %li", tmp, currPos);
					//myLogFile->AddLogMessage(&strLog, _ERROR);
				}
			}
		}
	}
	if (pMapFile[numberOfChars-1] == ',') lastComa = true;
	vecNumInts.push_back(tmp);
	numberOfInts++; 
	
	return numberOfInts;
}

	char outputArr[1024*1024*128*5];

int ConvertIntsToOutput ( char *outFile, vector<int> &vecOutInts, char *divider ) {
	int CurrPos = 0;
	//HANDLE hOutFile;
	FILE *fOutFile = fopen (outFile, "w");
	char endOfFile = '\0'; 
	/*hOutFile = CreateFileW((LPCWSTR) outFile, 
                       GENERIC_WRITE  ,          // open for reading
                       FILE_SHARE_WRITE,       // share for reading
                       NULL,                  // default security
                       CREATE_ALWAYS,         // existing file only
                       FILE_ATTRIBUTE_NORMAL, // normal file
                       NULL);                 // no attr. template
					   */
	char CharedInt[150];
	int numOfInts = (int)vecOutInts.size()-1;
	int numLen, j; 
	long pos = 0;

	for (int i = 0; i < numOfInts; i ++){
		itoa(vecOutInts[i], CharedInt, 10);
		numLen = strlen(CharedInt);
		for (j = 0; j < numLen; j ++)
			outputArr[pos ++] = CharedInt[j];
		outputArr[pos ++] = ',';
	}

	itoa(vecOutInts[(int)vecOutInts.size()-1], CharedInt, 10);
	numLen = strlen(CharedInt);
	for (j = 0; j < numLen; j ++)
		outputArr[pos ++] = CharedInt[j];
	outputArr[pos ++] = '\0';

	fprintf(stderr, "%d NUMBERS PROCESSED SUCCESSFULLY\n", numOfInts+1);
	//WriteFile(hOutFile, (LPCVOID) outputArr, strlen(outputArr), 0, 0);
	fprintf(fOutFile, "%s", outputArr);

	fclose(fOutFile);
	//delete [] outputArr;
	return 0;
}

void BestStrategy(char * outFile, int numberOfInts, vector<int> &vecOutInts, long *A){	
	FILE *iOutFile = fopen (outFile, "w");
	int numLen, pos = 0;
	for (int i = 0; i < numberOfInts; i ++)
		A[vecOutInts[i]] ++;
	char CharedInt[10];
	for (int i = 0; i < _MOD; i ++){
		itoa(i, CharedInt, 10);
		numLen = strlen(CharedInt);
		for (int j = 0; j < A[i]; j ++){
			for (int u = 0; u < numLen; u ++)
				outputArr[pos ++] = CharedInt[u];
			outputArr[pos ++] = ',';
		}
	}
	outputArr[pos] = '\0';
	fprintf(stderr, "%d NUMBERS PROCESSED SUCCESSFULLY\n", numberOfInts);
	//WriteFile(hOutFile, (LPCVOID) outputArr, strlen(outputArr), 0, 0);
	fprintf(iOutFile, "%s", outputArr);
	fclose(iOutFile);
}

int main(int argc, char *argv[]) {
	char* inpFile;
	char *outFile = new char;

	//FILE *fLogFile = fopen(fakeLogFile, "w+");
	if (argc == 1){
		fprintf(stderr, "%s", "FATAL: TOO FEW ARGUMENTS\n");
		return _TOO_FEW_ARGS;
	}
	if (argc == 2){
		inpFile = argv[1];
		strcpy(outFile, inpFile);
		strcat(outFile, "_OUTPUT.txt");
	}
	if (argc == 3){
		inpFile = argv[1];
		outFile = argv[2];
	}
	if (argc >= 4){
		inpFile = argv[1];
		outFile = argv[2];
		logLevel = atoi(argv[3]);
	}
	
	LogFile myLogFile;
	
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo); 
    DWORD BlockSize = sysInfo.dwAllocationGranularity * 512;
	if (logLevel <= _INFO){
		char blockMsg[_BUFFER_SIZE] = "Block Size";
		sprintf(blockMsg, "%s %d ", blockMsg, BlockSize);
		myLogFile.AddLogMessage (blockMsg, _INFO);
	}

	OFSTRUCT ofstructInpFile;
	HFILE hfInpFile = OpenFile((LPCSTR)inpFile, &ofstructInpFile, OF_EXIST);
	if (hfInpFile ==  HFILE_ERROR){
		fprintf(stderr, "%s", "FATAL: FILE NOT EXISTS\n");
		return _FILE_NOT_EXISTS;
	}
    hfInpFile = OpenFile((LPCSTR)inpFile, &ofstructInpFile, OF_READWRITE);
	if (hfInpFile == HFILE_ERROR){
		WORD errcodeOfStruct = ofstructInpFile.nErrCode;
		//DWORD errcodeLast = GetLastError();
		myLogFile.AddLogMessage("Open Input File", _ERROR);
		
		fprintf(stderr, "%s", "FATAL: ACCESS TO FILE DENIED\n");
		return _ACCESS_CHECK;
	}

	HANDLE hInpFile = (HANDLE) hfInpFile;
	if (hInpFile == INVALID_HANDLE_VALUE){
		myLogFile.AddLogMessage("Handle Input File", _ERROR);
	}

	DWORD dwInpFileSizeHigh, dwInpFileSizeLow;
	if ( (dwInpFileSizeLow = GetFileSize(hInpFile,&dwInpFileSizeHigh)) == 0 ){
		myLogFile.AddLogMessage("Size Input File", _ERROR);
		
		fprintf(stderr, "%s", "WARN: EMPTY INPUT FILE\n");
		return _EMPTY_FILE;
	}
	LARGE_INTEGER liInpFileSize;
	liInpFileSize.HighPart = (LONG) dwInpFileSizeHigh;
	liInpFileSize.LowPart = dwInpFileSizeLow;
	SIZE_T dwMapBlockSize;
	//if (liInpFileSize.LowPart < BlockSize && liInpFileSize.HighPart == 0) 
	dwMapBlockSize = liInpFileSize.LowPart;
	//else dwMapBlockSize = BlockSize;
	HANDLE hInpMap;
	hInpMap = CreateFileMappingW(hInpFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if (hInpMap == NULL){
		myLogFile.AddLogMessage("Handle Map Input File", _ERROR);
	}

    LPSTR bufInpData;
	bufInpData = (LPSTR)MapViewOfFile(hInpMap, FILE_MAP_READ, 0, 0, 0);
	if (bufInpData == NULL){
		DWORD LastErrorCode = GetLastError();
		myLogFile.AddLogMessage("Map View Input File", _ERROR);
	}
	char inpDivider = ',';
	char *outDivider = ",";
	bool first = 0;
	int tail = 0;

	vector<int> vecOutInts;
	vecOutInts.clear();
	
	
	long A[_MOD +5];

	if (dwInpFileSizeLow > _MAX_FILE_SIZE){
		bStratg = true;	
		memset(A, 0, sizeof(A));
	}
	
	int numberOfInts = ConvertInputToInts(bufInpData, liInpFileSize.QuadPart/sizeof(char), vecOutInts, tail, inpDivider, first, &myLogFile);
//	if (lastComa)
//		numberOfInts --;
	
	printf(">>FOUND %d NUMBERS\n", numberOfInts);
		
	if (bStratg){
		BestStrategy(outFile, numberOfInts, vecOutInts, A);
		return 0;
	}
	sort(vecOutInts.begin(), vecOutInts.end(), cmpFunc);
	printf(">>end sorting\n");

	LPSTR bufOutData;
	//bufOutData = (LPSTR)MapViewOfFile(hInpMap, FILE_MAP_ALL_ACCESS, 0, 0, dwMapBlockSize);
	
	UnmapViewOfFile(hInpMap);	
	CloseHandle(hInpFile);
	
	ConvertIntsToOutput(outFile, vecOutInts, outDivider);
	
	//myLogFile.PrintAllToLogFile();
	

	return 0;
}